Conversation
fiftin
commented
Apr 19, 2026
- feat(secrets): add sync flag and sync paths table
- fix(secrets): layout
- feat(secrets): auto sync
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: bc0ccade0c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Stale comment
Security review (PR 3793)
Outcome: No medium, high, or critical vulnerabilities were identified in the added or modified code after tracing attacker-controlled inputs to sinks and checking existing controls (parameterized SQL, project-scoped middleware, API field handling).
Prior threads: Previous automation findings were re-evaluated against the current diff; none warranted re-reporting with a concrete exploit path tied to this change set.
Notes (non-findings):
project__secret_syncinserts use bound parameters; path/prefix/separator values are not concatenated into SQL.- Manual
SyncSecretsuses storage from middleware; sync config is loaded bystorage_idwith the same scope.GetAppSysProcAttrapplies optional Linux namespaces only to app runners; git continues to useGetSysProcAttr(per code comments andCmdGitClient).- Pro
SyncSecretsis currently a stub returningnil; scheduler work is effectively no-op until implemented—treated as incomplete feature surface, not a demonstrated vulnerability in this PR.Sent by Cursor Automation: Find vulnerabilities
There was a problem hiding this comment.
Stale comment
Security review (sync flag refactor)
Outcome: One high finding remains: secret storage deletion can leave orphaned secrets in external secret backends when sync had imported keys.
Not reported:
SyncSecretsis a no-op in OSS/pro stub (no SSRF/injection from sync execution in this diff). SQL uses parameterized queries.Synchronizedis forced server-side on create/update. Linux namespace flags are opt-in config.Slack-ready summary: PR #3793 — 1 high: deleting a sync-enabled secret storage bulk-deletes imported keys with
DeleteAccessKeyonly, skippingDeleteSecret/ deserializer cleanup, so Vault/AWS SM/Azure KV entries can survive after the Semaphore row is gone.Sent by Cursor Automation: Find vulnerabilities
…or/sync-flag Gate secret sync scheduler to a single HA worker
There was a problem hiding this comment.
Security review (PR 3793)
Verdict: One high-severity issue remains: environment secret-sync persistence trusts client-supplied secret_storage_id without verifying the storage belongs to the same project as the environment, so a project member can point sync config at another project's secret storage (IDOR / cross-tenant data boundary). Other changes (parameterized SQL, sync stub, namespace split for app vs git, key synchronized hardening) did not surface additional high-confidence exploit paths in this diff.
Slack (copy/paste): PR 3793 — 1 High: cross-project secret_storage_id in saveEnvironmentSync lets env updates attach sync rows to arbitrary storage_id; fix by validating GetSecretStorage(env.ProjectID, *SecretStorageID) before SaveSecretSync. No other medium+ findings from this pass.
Sent by Cursor Automation: Find vulnerabilities
| EnvironmentID: &envID, | ||
| } | ||
| if env.SecretStorageID != nil { | ||
| sync.StorageID = *env.SecretStorageID |
There was a problem hiding this comment.
High — IDOR / cross-project secret storage binding
saveEnvironmentSync copies env.SecretStorageID from the JSON body into sync.StorageID without checking that that storage row belongs to env.ProjectID. A user with access to project A can set secret_storage_id to a storage ID from project B (guessed or observed); the FK only constrains storage_id to exist somewhere in project__secret_storage, not to match the environment's project. project_id on the sync row stays A while storage_id points at B's vault — wrong data boundary and a plausible path to future sync/import logic operating on another tenant's backend once implemented.
Fix: Before persisting, load the storage with GetSecretStorage(env.ProjectID, *env.SecretStorageID) (or equivalent) and reject on ErrNotFound / mismatch.

